/**
 * Advertiser.java
 *
 * @author Srdjan Petrovic
 * @author BJ Premore
 */


package SSF.OS.BGP4.Widgets;


import java.util.*;
import java.io.*;
import com.renesys.raceway.SSF.*;
import com.renesys.raceway.DML.*;


// ===== class SSF.OS.BGP4.Widgets.Advertiser ============================== //
/**
 * A test class, masquerading as a ProtocolSession, for injecting a given
 * workload into the system.  Workload is specified in a special file, which is
 * passed as an argument in the DML file.
 */
public class Advertiser extends ProtocolSession {

  // ..... member data ..................................................... //

  /** The BGPSession with which this tester is associated. */
  private BGPSession bgp;

  /** Name of the file where the workload information is stored. */
  private String workload_file;

  /** Time (in seconds) at which to start sending the workload. */
  private double start_time;

  // ..... header information ...............................................//

  /** IP address of the sender of the update message. */
  private IPaddress src_ip;

  /** AS number of the sender of the update message. */
  private int src_as;

  /** AS_PATH attribute value for the update message. */
  private ArrayList as_path = new ArrayList(2);

  /** ORIGIN attribute value for the update message. */
  private int origin;

  /** NEXT_HOP attribute value for the update message. */
  private IPaddress next_hop;

  /** MED attribute value for the update message. */
  private int med;

  /** LOCAL_PREF attribute value for the update message. */
  private int lpref;

  /** COMMUNITIES attribute value for the update message. */
  private ArrayList communities;

  /** ATOMIC_AGGREGATE value for the update message. */
  private boolean atomic_aggregate;

  /** IP address of the router which performed route aggregation. */
  private IPaddress agg_ip;

  /** AS number of the AS where the aggregation was performed. */
  private String agg_as;

  /** ORIGINATOR_ID attribute value for the update message. */
  private IPaddress orig_id;

  /** CLUSTER_LIST attribute value for the update message. */
  private ArrayList cluster_list = new ArrayList(0);

  // ----- Advertiser() ---------------------------------------------------- //
  /**
   * We must have a zero-argument, public constructor (so that
   * <code>newInstance()</code> can be used to create a new
   * <code>Advertiser</code> instance).
   */
  public Advertiser() {
  }

  // ----- config ---------------------------------------------------------- //
  /**
   * Set the configurable values (specified in DML file).
   *
   * @exception configException  if any of the calls to <code>find</code>
   *                             or <code>findSingle</code> throw such an
   *                             exception.
   */
  @Override
public void config(Configuration cfg) throws configException {
    super.config(cfg);

    String str;
    str = (String)cfg.findSingle("workload_file");
    if (str == null) {
      System.out.println("Advertiser missing 'workload_file' attribute");
    } else {
      workload_file = str;
    }
    str = (String)cfg.findSingle("start_time");
    if (str == null) {
      start_time = 0;
    } else {
      start_time = Double.parseDouble(str);
    }
  }

  // ----- init ------------------------------------------------------------ //
  /**
   * Creates an SSF process which parses the workload file, and withdraws or
   * announces at specified timestamps.
   */
  @Override
public void init() {
    new process(inGraph()) {
        @Override
		public boolean isSimple() {
          return false;
        }

        @Override
		public void action() {
          // get the pointer to the local BGP session
          try {
            bgp = (BGPSession)inGraph().SessionForName("bgp");
          } catch (ProtocolException pex) {
            throw new Error("Advertiser couldn't get handle to BGP");
          }

          UpdateMessage[] msg_array = new UpdateMessage[bgp.nbs.length-1];

          // parse the workload file
          try {
            BufferedReader bin =
                             new BufferedReader(new FileReader(workload_file));
            StringTokenizer st, pathst;
            String line, tmpstr;
            double timestamp = 0.0;
            double prevtimestamp = 0.0;
            IPaddress prefix = null;
            String record_type = null;
            boolean sending = false;

            waitFor(Net.seconds(start_time));
            
            // The timestamps in the file are each relative to the start time
            // (indicated with DML attribute 'start_time'), rather than to the
            // start of the simulation.

            line = bin.readLine();

            while (line != null) {
              st = new StringTokenizer(line,"|",true);
              record_type = st.nextToken();
              sending = false;

              if (record_type.equalsIgnoreCase("H")) {
                st.nextToken(); // skip "|"
                st.nextToken(); // skip Type value
                st.nextToken(); // skip "|"
                timestamp = Double.parseDouble(st.nextToken()); // timestamp
                st.nextToken(); // skip "|"
                src_ip = new IPaddress(st.nextToken()); // sender IP address
                st.nextToken(); // skip "|"
                src_as = Integer.parseInt(st.nextToken()); // sender AS number
                st.nextToken(); // skip "|"

                // read the AS_PATH values
                tmpstr = st.nextToken();
                if (!tmpstr.equals("|")) {
                  pathst = new StringTokenizer(tmpstr," ][");
                  while (pathst.hasMoreTokens()) {
                    as_path.add(pathst.nextToken());
                  }
                  st.nextToken(); // skip "|"
                }

                // read the origin value
                tmpstr = st.nextToken();
                if (tmpstr.equalsIgnoreCase("IGP")) {
                  origin = Origin.IGP;
                } else if (tmpstr.equalsIgnoreCase("INCOMPLETE")) {
                  origin = Origin.INC;
                } else if (tmpstr.equalsIgnoreCase("EGP")) {
                  origin = Origin.EGP;
                } else {
                  System.out.println("Error parsing workload file. " +
                                     "Invalid origin value.");
                  break;
                }

                st.nextToken(); // skip "|"

                // read the next_hop value
                tmpstr = st.nextToken();
                if (tmpstr.equals("none")) {
                  next_hop = new IPaddress(src_ip);
                } else {
                  next_hop = new IPaddress(tmpstr);
                }

                st.nextToken(); // skip "|"
                med = Integer.parseInt(st.nextToken()); // MED
                st.nextToken(); // skip "|"
                lpref = Integer.parseInt(st.nextToken()); // LOCAL_PREF
                st.nextToken(); // skip "|"

                // for now, we skip the communities list
                tmpstr = st.nextToken();
                if (!tmpstr.equals("|")) {
                  st.nextToken(); // skip communities list
                }

                // read ATOMIC_AGGREGATE value
                tmpstr = st.nextToken();
                if (tmpstr.equals("AG")) {
                  atomic_aggregate = true;
                } else if (tmpstr.equals("NAG")) {
                  atomic_aggregate = false;
                } else {
                  System.out.println("Error parsing workload file. " +
                                     "Invalid atomic aggregate value");
                  break;
                }

                st.nextToken(); // skip "|"

                // read agg_ip value (if any)
                tmpstr = st.nextToken();
                if (!tmpstr.equals("|")) {
                  agg_ip = new IPaddress(tmpstr);
                  st.nextToken(); // skip "|"
                }

                // read the agg_as value (if any)
                tmpstr = st.nextToken();
                if (!tmpstr.equals("|")) {
                  agg_as = tmpstr;
                  st.nextToken(); // skip "|"
                }

                // read the ORIGINATOR_ID value (if any)
                tmpstr = st.nextToken();
                if (!tmpstr.equals("|")) {
                  orig_id = new IPaddress(st.nextToken());
                  st.nextToken(); // skip "|"
                }

                // read the CLUSTER_LIST value (if any)
                tmpstr = st.nextToken();
                if (!tmpstr.equals("|")) {
                  pathst = new StringTokenizer(tmpstr);
                  while (pathst.hasMoreTokens()) {
                    cluster_list.add(pathst.nextToken());
                  }
                }

                // Done parsing the header.  Now parse the body of the update
                // message.
                for (int i=0; i<msg_array.length; i++) {
                  msg_array[i] = new UpdateMessage(bgp.nh);
                }
              } else if (record_type.equalsIgnoreCase("A")) {
                st.nextToken(); // skip "|"
                prefix = new IPaddress(st.nextToken()); // read prefix

                // create the route, and set the values for it
                Route rte = new Route();
                rte.set_nlri(prefix);
                if (!Global.basic_attribs) {
                  rte.set_origin(origin);
                } else {
                  Debug.gwarn("ignoring MED attribute because " +
                              "Global.basic_attribs option is in use");
                }
                for (int j=0;j<as_path.size();j++) {
                  rte.prepend_as((String)as_path.get(j));
                }
                rte.set_nexthop(next_hop);
                if (!Global.basic_attribs) {
                  rte.set_med(med);
                } else {
                  Debug.gwarn("ignoring MED attribute because " +
                              "Global.basic_attribs option is in use");
                }
                rte.set_localpref(lpref);
                if (atomic_aggregate) {
                  if (!Global.basic_attribs) {
                    rte.set_atomicagg();
                  } else {
                    Debug.gwarn("ignoring ATOMIC_AGGREGATE attribute because "+
                                "Global.basic_attribs option is in use");
                  }
                }
                if (agg_ip != null && agg_as != null) {
                  if (!Global.basic_attribs) {
                    rte.set_aggregator(agg_as,agg_ip);
                  } else {
                    Debug.gwarn("ignoring AGGREGATOR attribute because " +
                                "Global.basic_attribs option is in use");
                  }
                }
                if (orig_id != null) {
                  if (!Global.basic_attribs) {
                    rte.set_orig_id(orig_id);
                  } else {
                    Debug.gwarn("ignoring ORIGINATOR_ID attribute because " +
                                "Global.basic_attribs option is in use");
                  }
                }
                if (cluster_list.size() > 0) {
                  if (!Global.basic_attribs) {
                    for (int j=0;j<cluster_list.size();j++) {
                      rte.append_cluster(Long.parseLong((String)cluster_list.
                                                                      get(j)));
                    }
                  } else {
                    Debug.gwarn("ignoring CLUSTER_LIST attribute because " +
                                "Global.basic_attribs option is in use");
                  }
                }
                // add this route to the update messages
                for (int i=0; i<msg_array.length; i++) {
                  msg_array[i].add_route(rte);
                }
                sending = true;
              } else if (record_type.equalsIgnoreCase("W")) {
                st.nextToken(); // skip "|"
                prefix = new IPaddress(st.nextToken());
                for (int i=0; i<msg_array.length; i++) {
                  msg_array[i].add_wd(prefix);
                }
                sending = true;
              } else {
                System.out.println("ERROR: unexpected workload record type: " +
                                   record_type);
                System.exit(1);
              }

              if (sending) {
                if (timestamp != prevtimestamp) {
                  // wait before sending
                  waitFor(Net.seconds(timestamp-prevtimestamp));
                  prevtimestamp = timestamp;
                }

                // send the update message
                for (int i=0; i<bgp.nbs.length-1; i++) {//skip last nb ('self')
                  bgp.force_send(msg_array[i], bgp.nbs[i], 0);
                  bgp.reset_timer(bgp.nbs[i],
                                  SSF.OS.BGP4.Timing.Timer.KEEPALIVE);
                  bgp.reset_timer(bgp.nbs[i],SSF.OS.BGP4.Timing.Timer.HOLD);
                  // uncomment this to stagger updates
                  //waitFor(Net.seconds(0.5));
                }

                // reset all attributes
                src_ip = null;
                src_as = 0;
                as_path.clear();
                origin = -1;
                next_hop = null;
                med = 0;
                lpref = 0;
                atomic_aggregate = false;
                agg_ip = null;
                agg_as = null;
                orig_id = null;
                cluster_list.clear();
              }

              line = bin.readLine();
            } // end of main while loop

            waitForever();
          } catch (FileNotFoundException fnfe) {
            System.out.println("Error. Couldn't open the workload file.");
            waitForever();
          } catch (IOException io) {
            System.out.println("Error while reading the workload file.");
            waitForever();
          }
        }
      };
  }

  // ----- now ------------------------------------------------------------- //
  /**
   * Returns the current simulation time.  A convenience method so that
   * functions, not just Processes, can get the current simulation time.
   *
   * @return the current simulation time
   */
  public double now() {
    return ((double)(inGraph().now()))/((double)Net.frequency);
  }

  // ----- push ------------------------------------------------------------ //
  /**
   * Handles incoming events.
   */
  @Override
public boolean push(ProtocolMessage message, ProtocolSession fromSession) {
    // there will never be any incoming events in this pseudo-ProtocolSession
    throw new Error("Advertiser.push() called");
  }

} // end of class Advertiser
